"use strict";
const _ = require("lodash");
const ApiGateway = require("moleculer-web");
const isReadableStream			= require("isstream").isReadable;

/**
 * @typedef {import('moleculer').Context} Context Moleculer's Context
 * @typedef {import('http').IncomingMessage} IncomingRequest Incoming HTTP Request
 * @typedef {import('http').ServerResponse} ServerResponse HTTP Server Response
 */

module.exports = {
	name: "api",
	mixins: [ApiGateway],

	// More info about settings: https://moleculer.services/docs/0.14/moleculer-web.html
	settings: {
		// Exposed port
		port: process.env.PORT || 3000,

		// Exposed IP
		ip: "0.0.0.0",

		// Global Express middlewares. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Middlewares
		use: [],

		// Global CORS settings for all routes
		cors: {
			// Configures the Access-Control-Allow-Origin CORS header.
			origin: "*",
			// Configures the Access-Control-Allow-Methods CORS header.
			methods: ["GET", "OPTIONS", "POST", "PUT", "DELETE"],
			// Configures the Access-Control-Allow-Headers CORS header.
			allowedHeaders: ["Origin","X-Requested-With","Content-Type","Accept","Language",'Authorization','authorization','Token','token'],
			// Configures the Access-Control-Expose-Headers CORS header.
			// simple response headers："Cache-Control","Content-Language","Content-Length","Content-Type","Expires","Last-Modified","Pragma"
			exposedHeaders: ["Cache-Control","Content-Language","Content-Length","Content-Type","Expires","Last-Modified","Pragma","Authorization","DateFormat",'Authorization','authorization','Token','token'],
			// Configures the Access-Control-Allow-Credentials CORS header.
			credentials: true,
			// Configures the Access-Control-Max-Age CORS header.
			maxAge: 3600
		},

		routes: [
			{
				path: "/api",

				whitelist: [
					"**"
				],

				// Route-level Express middlewares. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Middlewares
				use: [],

				// Enable/disable parameter merging method. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Disable-merging
				mergeParams: true,

				// Enable authentication. Implement the logic into `authenticate` method. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Authentication
				authentication: false,

				// Enable authorization. Implement the logic into `authorize` method. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Authorization
				authorization: false,

				// The auto-alias feature allows you to declare your route alias directly in your services.
				// The gateway will dynamically build the full routes from service schema.
				autoAliases: true,

				aliases: {
					"POST /upload": {
						type: "multipart",
						// Action level busboy config
						busboyConfig: {
							limits: {
								files: 5,
								fileSize: 100 * 1024 * 1024
							},
							onPartsLimit(busboy, alias, svc) {
								this.logger.info("Busboy parts limit!", busboy);
							},
							onFilesLimit(busboy, alias, svc) {
								this.logger.info("Busboy file limit!", busboy);
							},
							onFieldsLimit(busboy, alias, svc) {
								this.logger.info("Busboy fields limit!", busboy);
							}
						},
						action: "v1.file.save"
					},
				},

				/**
				 * Before call hook. You can check the request.
				 * @param {Context} ctx
				 * @param {Object} route
				 * @param {IncomingRequest} req
				 * @param {ServerResponse} res
				 * @param {Object} data
				 * */
				onBeforeCall(ctx, route, req, res) {
					// Set request headers to context meta
					ctx.meta.userAgent = req.headers["user-agent"];
					ctx.meta.host = req.headers["host"];
					ctx.meta.protecol = `${this["isHTTPS"] ? "https" : "http"}`;
					ctx.meta.baseUrl = `${this["isHTTPS"] ? "https" : "http"}://${req.headers["host"]}${req["baseUrl"]}`;
				},

				/**
				 * After call hook. You can modify the data.
				 * @param {Context} ctx
				 * @param {Object} route
				 * @param {IncomingRequest} req
				 * @param {ServerResponse} res
				 * @param {Object} data
				 * */
				onAfterCall(ctx, route, req, res, data) {
					// Async function which return with Promise
					if(req.url.endsWith("/user/login") && data && data.token){//如果是登陆，则把 token 写到响应头里
						res.setHeader("token",data.token);
					}
					if (isReadableStream(data)) { //Stream response
						res.setHeader('content-Type',ctx.meta.$responseType || 'application/octet-stream');
						if(ctx.meta.$filename){
							res.setHeader('content-Disposition', `attachment;filename=${ctx.meta.$filename}`);
						}
						if(ctx.meta.$contentLength){
							res.setHeader('content-length', `${ctx.meta.$contentLength}`);
						}
						return data;
					}
					return {
						code: 200,
						message: 'success',
						data
					};
				},

				// Calling options. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Calling-options
				callingOptions: {},

				bodyParsers: {
					json: {
						strict: false,
						limit: "1MB"
					},
					urlencoded: {
						extended: true,
						limit: "1MB"
					}
				},

				// Mapping policy setting. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Mapping-policy
				mappingPolicy: "all", // Available values: "all", "restrict"

				// Enable/disable logging
				logging: true
			}
		],

		// Do not log client side errors (does not log an error response when the error.code is 400<=X<500)
		log4XXResponses: false,
		// Logging the request parameters. Set to any log level to enable it. E.g. "info"
		logRequestParams: null,
		// Logging the response data. Set to any log level to enable it. E.g. "info"
		logResponseData: null,


		// Serve assets from "public" folder. More info: https://moleculer.services/docs/0.14/moleculer-web.html#Serve-static-files
		assets: {
			folder: "public",

			// Options to `server-static` module
			options: {}
		},
		onError(req, res, err) {
			// Return with the error as JSON object
			res.setHeader("Content-type", "application/json; charset=utf-8");
			res.writeHead(err.code || 500);
			if (err.code == 422) {
				if(err.data && _.isArray(err.data) && err.data.length > 0) {
					err.data.forEach(e => {
						delete e[ "nodeID" ];
						delete e[ "action" ];
					});
				}
				const errObj = _.pick(err, ["code", "message", "data"]);
				res.end(JSON.stringify(errObj, null, 2));
			} else {
				const errObj = _.pick(err, [ "message", "code","data"]);
				errObj["code"] = errObj["code"] || err.code || 500;
				res.end(JSON.stringify(errObj, null, 2));
			}
		}
	},

	methods: {

		/**
		 * Authenticate the request. It check the `Authorization` token value in the request header.
		 * Check the token value & resolve the user by the token.
		 * The resolved user will be available in `ctx.meta.user`
		 *
		 * PLEASE NOTE, IT'S JUST AN EXAMPLE IMPLEMENTATION. DO NOT USE IN PRODUCTION!
		 *
		 * @param {Context} ctx
		 * @param {Object} route
		 * @param {IncomingRequest} req
		 * @returns {Promise}
		 */
		async authenticate(ctx, route, req) {
			let action = req["$action"];
			//在 action 中添加 auth required 和 ignore 分别表示需要登陆验证  和  忽略验证
			let authRequired = action.auth || action.service.settings.auth || 'required';
			if(authRequired == 'required'){
				const auth = req.headers["authorization"];  // Read the token from header
				if (auth && auth.startsWith("Bearer")) {
					const authArray = auth.split(/\s+/);
					// Check the token. Tip: call a service which verify the token. E.g. `accounts.resolveToken`
					if (authArray.length > 1) {
						const token = authArray[1];
						let user;
						try {
							user = await ctx.broker.call("v1.user.resolveToken", { token });
							if (user) {
								this.logger.info("Authenticated via JWT: ", user.username);
								// Reduce user fields (it will be transferred to other nodes)
								ctx.meta.user = user;
								ctx.meta.token = token;
								ctx.meta.userId = user.id;
							}
						} catch (err) {
							// Ignored because we continue processing if user doesn't exists
						}
						return user;
					} else {
						// Invalid token
						throw new ApiGateway.Errors.UnAuthorizedError(ApiGateway.Errors.ERR_INVALID_TOKEN,[]);
					}
				} else {
					// No token. Throw an error or do nothing if anonymous access is allowed.
					throw new ApiGateway.Errors.UnAuthorizedError(ApiGateway.Errors.ERR_NO_TOKEN,[]);
				}
			}
			return null;
		},

		/**
		 * Authorize the request. Check that the authenticated user has right to access the resource.
		 *
		 * PLEASE NOTE, IT'S JUST AN EXAMPLE IMPLEMENTATION. DO NOT USE IN PRODUCTION!
		 *
		 * @param {Context} ctx
		 * @param {Object} route
		 * @param {IncomingRequest} req
		 * @returns {Promise}
		 */
		async authorize(ctx, route, req) {
			// Get the authenticated user.
			const user = ctx.meta.user;
			let action = req["$action"];
			// It check the `auth` property in action schema.
			if (action.auth == "required" && !user) {
				throw new ApiGateway.Errors.UnAuthorizedError("NO_RIGHTS",[]);
			}
		}

	}
};
